package cur.channel.client.netty;

import com.alibaba.fastjson.JSON;
import cur.channel.client.model.GroupLoginMsg;
import cur.channel.client.model.GroupTaskMsg;
import cur.channel.client.model.Message;
import cur.channel.client.util.CommonUtils;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.CharsetUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * Created by gantianxing on 2017/7/20.
 */
@Component
public class NettyManager {
    private static final Log log = LogFactory.getLog(NettyManager.class);
    private ScheduledExecutorService executorReConService;
    private ScheduledExecutorService executorTaskService;
    @Resource
    private NettyClientHandler handler;
    @Resource
    private NettyProperties nettyProperties;

    /**
     * netty服务启动方法
     */
    @PostConstruct
    public void init(){

        //建立连接
        this.connectServers();
        //启动定时节点获取worker
//        this.taskReq();
        //启动重连worker
        this.reConnect();
    }


    /**
     * netty服务销毁方法
     */
    @PreDestroy
    public void destroy(){
        this.relaseAll();
    }

//    --------------------wujian--------------------------------------

    /**
     * 遍历服务器列表，建立长连接
     */
    public void connectServers(){
        if(null==nettyProperties.getUrl() || nettyProperties.getUrl().size()==0){
            return;
        }
        nettyProperties.getUrl().forEach(url->{
            regToServer(url);
        });
    }


    /**
     * 检查服务列表,把新的服务放入重连map
     * @param serverInfo 格式serverId:serverPort
     */
    private void regToServer(String serverInfo){
        //step1 检查serverInfo是否已经在 有效的server map里
        if(NettyServerMap.getValid(serverInfo)!=null){ return ; }

        //step2与服务端建立连接
        if(NettyServerMap.getInvalid(serverInfo) == null){
            String oneServer [] = serverInfo.split("\\:");
            String ip = oneServer[0];
            int port = Integer.valueOf(oneServer[1]).intValue();
            NettyEntity nettyClient = new NettyEntity(port,ip,handler,nettyProperties);
            nettyClient.connect();
            //连接成功方有效map，否则放无效map
            if(nettyClient.channel == null){
                NettyServerMap.addInValid(serverInfo, nettyClient);
            }else {
                NettyServerMap.addValid(serverInfo, nettyClient);
            }
        }
    }

    /**
     * 定时重链接无效的服务器列表
     */
    public void reConnect(){
        executorReConService = Executors.newScheduledThreadPool(1);
        executorReConService.scheduleWithFixedDelay(new Runnable() {
            public void run() {
                try {
                    Map<String, NettyEntity> invalid = NettyServerMap.getAllInvalid();
                    for (String key : invalid.keySet()) {
                        NettyEntity clientInfo = invalid.get(key);
                        clientInfo.connect();

                        //连接成功后
                        if (clientInfo.channel != null) {
                            NettyServerMap.removeInvalid(key);
                            NettyServerMap.addValid(key, clientInfo);
                        }
                    }
                }catch (Exception e){
                    log.error("定时重连失败",e);
                }
            }
        }, 0, nettyProperties.getReconnectWaitSecond(), TimeUnit.SECONDS);
    }


    /**
     * 定时随机取一台有效的服务器发起请求
     */
    public void taskReq() {
        executorTaskService = Executors.newScheduledThreadPool(1);
        executorTaskService.scheduleWithFixedDelay(new Runnable() {
            public void run() {
                try{
                    NettyEntity nettyClient = NettyServerMap.getRandomChannl();
                    if(nettyClient!=null){
                        if (nettyClient.channel != null) {
                            log.info("random send task.server address:"+nettyClient.channel.remoteAddress());
                            GroupTaskMsg taskMsg = new GroupTaskMsg();
                            taskMsg.setGroupId(nettyProperties.getGroupId());
                            taskMsg.setClientId(NettyProperties.clientId);
                            nettyClient.channel.writeAndFlush(JSON.toJSONString(new Message(taskMsg)));
                        }
                    }else
                    {
                        log.error("没有可用的服务");
                    }
                } catch (Exception e){
                    log.error("定时发起task任务失败",e);
                }
            }
        }, nettyProperties.getTaskRequestWaitTime(), nettyProperties.getTaskRequestWaitTime(), TimeUnit.SECONDS);
    }

    /**
     * 释放资源
     */
    public void relaseAll(){
        Map<String, NettyEntity> valid = NettyServerMap.getAllValid();
        for(String key: valid.keySet()){
            NettyEntity clientInfo = valid.get(key);
            if(clientInfo.channel !=null){
                clientInfo.releaseConnect();
            }
        }

        Map<String, NettyEntity> invalid = NettyServerMap.getAllInvalid();
        for(String key: invalid.keySet()){
            NettyEntity clientInfo = invalid.get(key);
            if(clientInfo.channel !=null){
                clientInfo.releaseConnect();
            }
        }
    }

//    --------------------wujian--------------------------------------

    public static class NettyEntity {
        private static final Log log = LogFactory.getLog(NettyEntity.class);
        private int port;
        private String host;
        private EventLoopGroup group;
        Bootstrap bootstrap;
        public Channel channel;
        private NettyClientHandler businessHander;
        private NettyProperties nettyProperties;

        public NettyEntity(int port, String host, NettyClientHandler businessHander, NettyProperties nettyProperties){
            this.host = host ;
            this.port = port ;
            this.businessHander = businessHander;
            this.nettyProperties = nettyProperties;
            group = new NioEventLoopGroup();
            bootstrap = new Bootstrap();
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
            bootstrap.group(group);
            bootstrap.handler(new ChildChannelHandler(nettyProperties,businessHander));
        }

        /**
         * 连接服务器
         */
        public void connect() {
            try {
                // 连接服务端
                if (channel != null && channel.isOpen()) {
                    channel.close();
                }
                ChannelFuture future = bootstrap.connect(host, port).sync();
                channel = future.channel();

                // 此方法会阻塞
                //channel.closeFuture().sync();
                if (future.isSuccess()) {
                    log.info("connect server success-" + channel.remoteAddress());

                    //登陆服务器
                    GroupLoginMsg loginMsg = new GroupLoginMsg();
                    loginMsg.setClientId(NettyProperties.clientId);
                    loginMsg.setPassword(nettyProperties.getPassword());
                    loginMsg.setUserName(nettyProperties.getUsername());
                    loginMsg.setGroupId(nettyProperties.getGroupId());
                    channel.writeAndFlush(JSON.toJSONString(new Message(loginMsg)));
                    log.info("login,server address-" + channel.remoteAddress());
                }

            } catch (Exception e) {
                log.error("connect server failed-"+host+":"+port);
                if (channel != null && channel.isOpen()) {
                    channel.close();
                }
                channel = null;
            }
        }
        /**
         * 断开链接，释放资源
         */
        public void releaseConnect(){
            if(channel!=null&&channel.isOpen()){
                log.info("releaseConnect,server address:" + channel.remoteAddress());
                channel.close();
            }
            if(group!=null){
                group.shutdownGracefully();
            }

        }

        public NettyProperties getNettyProperties() {
            return nettyProperties;
        }
    }

    private static class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
        private NettyProperties nettyProperties;
        private NettyClientHandler businessHander;
        public ChildChannelHandler(NettyProperties nettyProperties, NettyClientHandler businessHander) {
            super();
            this.nettyProperties = nettyProperties;
            this.businessHander = businessHander;
        }
        @Override
        protected void initChannel(SocketChannel socketChannel) throws Exception {
//            socketChannel.pipeline().addLast(new ObjectEncoder());
//            socketChannel.pipeline().addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(null)));
            socketChannel.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8));
            socketChannel.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8));
            socketChannel.pipeline().addLast(new IdleStateHandler(nettyProperties.getReadWaitSecond(), nettyProperties.getWriteWaitSecond(), 0, TimeUnit.SECONDS));
            socketChannel.pipeline().addLast(businessHander);
        }
    }
}
